//
// Created by Administrator on 2021/5/23.
//

#include "fmacros.h"
#include "socket.h"

#include <sys/stat.h>
#include <sys/un.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static void anetSetError(char *err, const char *fmt, ...) {
    va_list ap;

    if (!err) {
        return;
    }
    va_start(ap, fmt);
    vsnprintf(err, ANET_ERR_LEN, fmt, ap);
    va_end(ap);
}

static int anetSetBlock(char *err, int fd, int non_block) {
    int flags;

    if ((flags = fcntl(fd, F_GETFL)) == -1) {
        anetSetError(err, "fcntl(F_GETFL): %s", strerror(errno));
        return ANET_ERR;
    }

    if (non_block) {
        flags |= O_NONBLOCK;
    } else {
        flags &= ~O_NONBLOCK;
    }

    if (fcntl(fd, F_SETFL, flags) == -1) {
        anetSetError(err, "fcntl(F_SETFL, O_NONBLOCK): %s", strerror(errno));
        return ANET_ERR;
    }
    return ANET_OK;
}

int anetNonBlock(char *err, int fd) {
    return anetSetBlock(err, fd, 1);
}

int anetBlock(char *err, int fd) {
    return anetSetBlock(err, fd, 0);
}

static int anetSetTcpNoDelay(char *err, int fd, int val) {
    if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) == -1) {
        anetSetError(err, "setsockopt TCP_NODELAY: %s", strerror(errno));
        return ANET_ERR;
    }
    return ANET_OK;
}

int anetEnableTcpNoDelay(char *err, int fd) {
    return anetSetTcpNoDelay(err, fd, 1);
}

int anetDisableTcpNoDelay(char *err, int fd) {
    return anetSetTcpNoDelay(err, fd, 0);
}

int anetSetSendBuffer(char *err, int fd, int buffsize) {
    if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize)) == -1) {
        anetSetError(err, "setsockopt SO_SNDBUF: %s", strerror(errno));
        return ANET_ERR;
    }
    return ANET_OK;
}

static int anetSetReuseAddr(char *err, int fd) {
    int yes = 1;
    /* Make sure connection-intensive things like the redis benckmark
     * will be able to close/open sockets a zillion of times */
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) {
        anetSetError(err, "setsockopt SO_REUSEADDR: %s", strerror(errno));
        return ANET_ERR;
    }
    return ANET_OK;
}

#define ANET_CONNECT_NONE 0
#define ANET_CONNECT_NONBLOCK 1
#define ANET_CONNECT_BE_BINDING 2 /* Best effort binding. */
static int anetTcpGenericConnect(char *err, char *addr, int port, char *source_addr, int flags) {
    int s = ANET_ERR, rv;
    char portstr[6];
    struct addrinfo hints, *servinfo, *bservinfo, *p, *b;

    snprintf(portstr, sizeof(portstr), "%d", port);
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((rv = getaddrinfo(addr, portstr, &hints, &servinfo)) != 0) {
        anetSetError(err, "%s", gai_strerror(rv));
        return ANET_ERR;
    }
    for (p = servinfo; p != NULL; p = p->ai_next) {
        if ((s = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            continue;
        }
        if (anetSetReuseAddr(err, s) == ANET_ERR) {
            goto error;
        }
        if (flags & ANET_CONNECT_NONBLOCK && anetNonBlock(err, s) != ANET_OK) {
            goto error;
        }
        if (source_addr) {
            int bound = 0;
            if ((rv = getaddrinfo(source_addr, NULL, &hints, &bservinfo)) != 0) {
                anetSetError(err, "%s", gai_strerror(rv));
                goto error;
            }
            for (b = bservinfo; b != NULL; b = b->ai_next) {
                if (bind(s, b->ai_addr, b->ai_addrlen) != -1) {
                    bound = 1;
                    break;
                }
            }
            freeaddrinfo(bservinfo);
            if (!bound) {
                anetSetError(err, "bind: %s", strerror(errno));
                goto error;
            }
        }
        if (connect(s, p->ai_addr, p->ai_addrlen) == -1) {
            if (errno == EINPROGRESS && flags & ANET_CONNECT_NONBLOCK) {
                goto end;
            }
            close(s);
            s = ANET_ERR;
            continue;
        }

        goto end;
    }
    if (p == NULL) {
        anetSetError(err, "creating socket: %s", strerror(errno));
    }

    error:
    if (s != ANET_ERR) {
        close(s);
        s = ANET_ERR;
    }

    end:
    freeaddrinfo(servinfo);

    if (s == ANET_ERR && source_addr && (flags & ANET_CONNECT_BE_BINDING)) {
        return anetTcpGenericConnect(err, addr, port, NULL, flags);
    } else {
        return s;
    }

    return ANET_ERR;
}

int anetTcpConnect(char *err, char *addr, int port) {
    return anetTcpGenericConnect(err, addr, port, NULL, ANET_CONNECT_NONE);
}

int anetTcpNonBlockConnect(char *err, char *addr, int port) {
    return anetTcpGenericConnect(err, addr, port, NULL, ANET_CONNECT_NONBLOCK);
}

int anetRead(int fd, char *buf, int count) {
    int nread, totlen = 0;
    while (totlen != count) {
        nread = read(fd, buf, count - totlen);
        if (nread == 0) {
            return totlen;
        }
        if (nread == -1) {
            return -1;
        }
        totlen += nread;
        buf += nread;
    }

    return totlen;
}

int anetWrite(int fd, char *buf, int count) {
    int nwritten, totlen = 0;
    while (totlen != count) {
        nwritten = write(fd, buf, count - totlen);
        if (nwritten == 0) {
            return totlen;
        }
        if (nwritten == -1) {
            return -1;
        }
        totlen += nwritten;
        buf += nwritten;
    }

    return totlen;
}

static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog) {
    if (bind(s, sa, len) == -1) {
        anetSetError(err, "bind: %s", strerror(errno));
        close(s);
        return ANET_ERR;
    }

    if (listen(s, backlog) == -1) {
        anetSetError(err, "listen: %s", strerror(errno));
        close(s);
        return ANET_ERR;
    }

    return ANET_OK;
}

static int anetV6Only(char *err, int s) {
    int yes = 1;
    if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) == -1) {
        anetSetError(err, "setsockopt: %s", strerror(errno));
        close(s);
        return ANET_ERR;
    }

    return ANET_OK;
}

static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog) {
    int s, rv;
    char _port[6];  /* strlen("65535") */
    struct addrinfo hints, *servinfo, *p;

    snprintf(_port, 6, "%d", port);
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = af;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;

    if ((rv = getaddrinfo(bindaddr, _port, &hints, &servinfo)) != 0) {
        anetSetError(err, "%s", gai_strerror(rv));
        return ANET_ERR;
    }

    for (p = servinfo; p != NULL; p = p->ai_next) {
        if ((s = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            continue;
        }
        if (af == AF_INET6 && anetV6Only(err, s) == ANET_ERR) {
            goto error;
        }
        if (anetSetReuseAddr(err, s) == ANET_ERR) {
            goto error;
        }
        if (anetListen(err, s, p->ai_addr, p->ai_addrlen, backlog) == ANET_ERR) {
            goto error;
        }
        goto end;
    }
    if (p == NULL) {
        anetSetError(err, "unable to bind socket");
        goto error;
    }

    error:
    s = ANET_ERR;

    end:
    freeaddrinfo(servinfo);

    return s;
}

int anetTcpServer(char *err, int port, char *bindaddr, int backlog) {
    return _anetTcpServer(err, port, bindaddr, AF_INET, backlog);
}

int anetTcp6Server(char *err, int port, char *bindaddr, int backlog) {
    return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog);
}

static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *len) {
    int fd;
    while (1) {
        fd = accept(s, sa, len);
        if (fd == -1) {
            if (errno == EINTR) {
                continue;
            } else {
                anetSetError(err, "accept: %s", strerror(errno));
                return ANET_ERR;
            }
        }
        break;
    }
    return fd;
}

int anetTcpAccept(char *err, int s, char *ip, socklen_t ip_len, int *port) {
    int fd;
    struct sockaddr_storage sa;
    socklen_t salen = sizeof(sa);

    if ((fd = anetGenericAccept(err, s, (struct sockaddr *) &sa, &salen)) == -1) {
        return ANET_ERR;
    }

    if (sa.ss_family == AF_INET) {
        struct sockaddr_in *s = (struct sockaddr_in *) &sa;
        if (ip) {
            inet_ntop(AF_INET, (void *) &(s->sin_addr), ip, ip_len);
        }
        if (port) {
            *port = ntohs(s->sin_port);
        }
    } else {
        struct sockaddr_in6 *s = (struct sockaddr_in6 *) &sa;
        if (ip) {
            inet_ntop(AF_INET6, (void *) &(s->sin6_addr), ip, ip_len);
        }
        if (port) {
            *port = ntohs(s->sin6_port);
        }
    }

    return fd;
}

/* Format an IP,port pair into something easy to parse. If IP is IPv6
 * (matches for ":"), the ip is surrounded by []. IP and port are just
 * separated by colons. This the standard to display addresses within Redis. */
static int anetFormatAddr(char *buf, size_t buf_len, char *ip, int port) {
    return snprintf(buf, buf_len, strchr(ip, ':') ? "[%s]:%d" : "%s:%d", ip, port);
}

static int anetPeerToString(int fd, char *ip, socklen_t ip_len, int *port) {
    struct sockaddr_storage sa;
    socklen_t salen = sizeof(sa);

    if (getpeername(fd, (struct sockaddr *) &sa, &salen) == -1) {
        goto error;
    }
    if (ip_len == 0) {
        goto error;
    }

    if (sa.ss_family == AF_INET) {
        struct sockaddr_in *s = (struct sockaddr_in *) &sa;
        if (ip) {
            inet_ntop(AF_INET, (void *) &(s->sin_addr), ip, ip_len);
        }
        if (port) {
            *port = ntohs(s->sin_port);
        }
    } else if (sa.ss_family == AF_INET6) {
        struct sockaddr_in6 *s = (struct sockaddr_in6 *) &sa;
        if (ip) {
            inet_ntop(AF_INET6, (void *) &(s->sin6_addr), ip, ip_len);
        }
        if (port) {
            *port = ntohs(s->sin6_port);
        }
    } else if (sa.ss_family == AF_UNIX) {
        if (ip) {
            strncpy(ip, "/unixsocket", ip_len);
        }
        if (port) {
            *port = 0;
        }
    } else {
        goto error;
    }

    return 0;

    error:
    if (ip) {
        if (ip_len >= 2) {
            ip[0] = '?';
            ip[1] = '\0';
        } else if (ip_len == 1) {
            ip[0] = '\0';
        }
    }
    if (port) {
        *port = 0;
    }

    return -1;
}

static int anetSockName(int fd, char *ip, socklen_t ip_len, int *port) {
    struct sockaddr_storage sa;
    socklen_t salen = sizeof(sa);

    if (getsockname(fd, (struct sockaddr *) &sa, &salen) == -1) {
        if (port) *port = 0;
        ip[0] = '?';
        ip[1] = '\0';
        return -1;
    }
    if (sa.ss_family == AF_INET) {
        struct sockaddr_in *s = (struct sockaddr_in *) &sa;
        if (ip) {
            inet_ntop(AF_INET, (void *) &(s->sin_addr), ip, ip_len);
        }
        if (port) {
            *port = ntohs(s->sin_port);
        }
    } else {
        struct sockaddr_in6 *s = (struct sockaddr_in6 *) &sa;
        if (ip) {
            inet_ntop(AF_INET6, (void *) &(s->sin6_addr), ip, ip_len);
        }
        if (port) {
            *port = ntohs(s->sin6_port);
        }
    }

    return 0;
}

/* Like anetFormatAddr() but extract ip and port from the socket's peer. */
int anetFormatPeer(int fd, char *buf, size_t buf_len) {
    char ip[INET6_ADDRSTRLEN];
    int port;

    anetPeerToString(fd, ip, sizeof(ip), &port);

    return anetFormatAddr(buf, buf_len, ip, port);
}

int anetFormatSock(int fd, char *buff, size_t buff_len) {
    char ip[INET6_ADDRSTRLEN];
    int port;

    anetSockName(fd, ip, sizeof(ip), &port);

    return anetFormatAddr(buff, buff_len, ip, port);
}